Clear() 和 Resize()

我们继续为物流公司构建托盘跟踪器,假设还需要跟踪新托盘并从跟踪中删除旧托盘。如何实现这一操作呢?

使用数组方法清除数组并调整其大小

使用 Array.Clear() 方法可删除数组中特定元素的内容,使用 Array.Resize() 方法可在数组中添加或删除元素。

步骤 1 - 清除数组中的项

string[] pallets = { "B14", "A11", "B12", "A13" };
Console.WriteLine("");

Array.Clear(pallets, 0, 2);
Console.WriteLine($"Clearing 2 ... count: {pallets.Length}");
foreach (var pallet in pallets)
{
    Console.WriteLine($"-- {pallet}");
}

关注 Array.Clear(pallets, 0, 2); 所在的代码行。此处,使用 Array.Clear() 方法来清除存储在 pallets 数组元素中的值(从索引 0 开始,清除 2 个元素)。

运行代码时,你会发现清除了存储在数组内前两个元素中的值。从 Length 属性和 foreach 语句可知,这些元素仍然存在,但当前为空。

Clearing 2 ... count: 4
--
--
-- B12
-- A13

空字符串与 null

使用 Array.Clear() 时,被清除的元素不再引用内存中的字符串。事实上,该元素不指向任何内容。这是一个重要的区别,最初可能会难以理解。

如果尝试检索受 Array.Clear() 方法影响的某个元素的值呢?能检索到吗?

步骤 2 - 访问已清除元素的值

我们将通过两种方法访问已清除元素的值,以便查看 C# 编译器如何处理 null 值。

在调用 Array.Clear() 方法前后添加两行代码:

Console.WriteLine($"Before: {pallets[0]}");
Array.Clear(pallets, 0, 2);
Console.WriteLine($"After: {pallets[0]}");

请确保代码与以下代码列表相匹配:

string[] pallets = { "B14", "A11", "B12", "A13" };
Console.WriteLine("");

Console.WriteLine($"Before: {pallets[0]}");
Array.Clear(pallets, 0, 2);
Console.WriteLine($"After: {pallets[0]}");

Console.WriteLine($"Clearing 2 ... count: {pallets.Length}");
foreach (var pallet in pallets)
{
    Console.WriteLine($"-- {pallet}");
}

运行代码时,应会看到以下输出:

Before: B14
After:
Clearing 2 ... count: 4
--
--
-- B12
-- A13

如果关注 After: 输出行,你可能会认为 pallets[0] 中存储的值是空字符串。但是,C# 编译器会将 null 值隐式转换为可供显示的空字符串。

步骤 3 - 在已清除元素上调用字符串帮助器方法

为了证明清除后存储在 pallets[0] 中的值为 null,我们修改代码示例,在 pallets[0] 上调用 ToLower() 方法。如果它是字符串,代码应会正常工作。但如果它为 null,则会使代码引发异常。

更新代码,在每次尝试向控制台写入 pallets[0] 时添加对 ToLower() 方法的调用:

Console.WriteLine($"Before: {pallets[0].ToLower()}");
Array.Clear(pallets, 0, 2);
Console.WriteLine($"After: {pallets[0].ToLower()}");

请确保代码与以下代码列表相匹配:

string[] pallets = { "B14", "A11", "B12", "A13" };
Console.WriteLine("");

Console.WriteLine($"Before: {pallets[0].ToLower()}");
Array.Clear(pallets, 0, 2);
Console.WriteLine($"After: {pallets[0].ToLower()}");

Console.WriteLine($"Clearing 2 ... count: {pallets.Length}");
foreach (var pallet in pallets)
{
    Console.WriteLine($"-- {pallet}");
}

这次运行代码时,会收到一条很长的错误消息。如果分析该文本,将看到以下消息:

System.NullReferenceException: Object reference not set to an instance of an object.

编译器引发此异常是因为当我们尝试在 pallets[0] 元素的内容上调用方法时,C# 编译器尚未将 null 隐式转换为空字符串。

由此可知,Array.Clear() 会删除数组元素对值的引用(如果存在)。若要解决此问题,可以在尝试打印值之前检查该值是否为 null。

为实现此目的,可以在访问可能为 null 的数组元素之前添加 if 语句。

if (pallets[0] != null)
    Console.WriteLine($"After: {pallets[0].ToLower()}");

步骤 4 - 调整数组大小以添加更多元素

接下来,重写步骤 1 中的代码列表,使其包含用于调整数组大小的额外代码。完成后,代码应与以下代码列表一致:

string[] pallets = { "B14", "A11", "B12", "A13" };
Console.WriteLine("");

Array.Clear(pallets, 0, 2);
Console.WriteLine($"Clearing 2 ... count: {pallets.Length}");
foreach (var pallet in pallets)
{
    Console.WriteLine($"-- {pallet}");
}

Console.WriteLine("");
Array.Resize(ref pallets, 6);
Console.WriteLine($"Resizing 6 ... count: {pallets.Length}");

pallets[4] = "C01";
pallets[5] = "C02";

foreach (var pallet in pallets)
{
    Console.WriteLine($"-- {pallet}");
}

关注 Array.Resize(ref pallets, 6); 所在的行。在此,我们将调用通过引用(使用 ref 关键字)传入 pallets 数组的 Resize() 方法。在某些情况下,方法会要求按值(默认)或按引用(使用 ref 关键字)来传递参数。要解释此操作的必要性,需详细阐释 .NET 中对象的管理方式;很遗憾,这超出了本模块的范围。如果有疑问,建议查看 Intellisense 或 Microsoft Learn,获取有关如何正确调用给定方法的示例。

在本例中,我们要将 pallets 数组从 4 个元素调整为 6 个。将新元素添加到当前元素的末尾。在向这两个新元素赋值之前,其值为 null。

运行代码时,应会看到以下输出:

Clearing 2 ... count: 4
--
--
-- B12
-- A13

Resizing 6 ... count: 6
--
--
-- B12
-- A13
-- C01
-- C02

步骤 5 - 调整数组大小以删除元素

相反,可以使用 Array.Resize() 来删除数组元素:

string[] pallets = { "B14", "A11", "B12", "A13" };
Console.WriteLine("");

Array.Clear(pallets, 0, 2);
Console.WriteLine($"Clearing 2 ... count: {pallets.Length}");
foreach (var pallet in pallets)
{
    Console.WriteLine($"-- {pallet}");
}

Console.WriteLine("");
Array.Resize(ref pallets, 6);
Console.WriteLine($"Resizing 6 ... count: {pallets.Length}");

pallets[4] = "C01";
pallets[5] = "C02";

foreach (var pallet in pallets)
{
    Console.WriteLine($"-- {pallet}");
}

Console.WriteLine("");
Array.Resize(ref pallets, 3);
Console.WriteLine($"Resizing 3 ... count: {pallets.Length}");

foreach (var pallet in pallets)
{
    Console.WriteLine($"-- {pallet}");
}

运行代码时,应会看到以下输出:

Clearing 2 ... count: 4
--
--
-- B12
-- A13

Resizing 6 ... count: 6
--
--
-- B12
-- A13
-- C01
-- C02

Resizing 3 ... count: 3
--
--
-- B12

请注意,调用 Array.Resize() 不会删除前两个 null 元素,而会删除后三个元素,尽管它们含有字符串值。

是否能从数组中删除 null 元素?

如果 Array.Resize() 方法不能从数组中删除空元素,是否存在其他可自动执行此操作的帮助器方法?没有。实现此操作的最佳方式是,通过循环访问每个项并使一个变量(计数器)递增,对非 null 元素进行计数。接下来,创建另一个与计数器变量大小相同的数组。最后,循环访问原始数组中的每个元素,并将非 null 值复制到新数组中。

概括

以下是本单元中介绍的几处重要内容: